/* ------------------------------- editor.js -------------------------------- */
(function() {
   // Override the basic constructor defined at editor_basic.js.
   var Editor = VED.editor.extend({
      initialize: function( instanceConfig, element, mode ) {
         // Call the VED.event constructor to initialize this instance.
         this.parent.parent.call( this );

         // Make a clone of the config object, to avoid having it touched by our code. (#9636)
         instanceConfig = instanceConfig && VED.tools.clone( instanceConfig );

         // if editor is created off one page element.
         if ( element !== undefined ) {
            // Asserting element and mode not null.
            if ( !( element instanceof VED.dom.element ) )
               throw new Error( 'Expect element of type VED.dom.element.' );
            else if ( !mode )
               throw new Error( 'One of the element mode must be specified.' );

            if ( VED.env.ie && VED.env.quirks && mode == VED.ELEMENT_MODE_INLINE ) {
               throw new Error( 'Inline element mode is not supported on IE quirks.' );
            }

            // Asserting element DTD depending on mode.
            if ( mode == VED.ELEMENT_MODE_INLINE && !element.is( VED.dtd.$editable ) || mode == VED.ELEMENT_MODE_REPLACE && element.is( VED.dtd.$nonBodyContent ) )
               throw new Error( 'The specified element mode is not supported on element: "' + element.getName() + '".' );

            this.element = element;
            this.elementMode = mode;
            this.name = ( this.elementMode != VED.ELEMENT_MODE_APPENDTO ) && ( element.getId() || element.getNameAtt() );
         }
         else
            this.elementMode = VED.ELEMENT_MODE_NONE;

         // Declare the private namespace.
         this._ = {};

         this.commands = {};
         this.templates = {};
         this.name = this.name || genEditorName();
         this.id = VED.tools.getNextId();
         this.config = VED.tools.prototypedCopy( VED.config );
         this.ui = new VED.ui( this );
         this.focusManager = new VED.focusManager( this );
         this.keystrokeHandler = new VED.keystrokeHandler( this );

         // Make the editor update its command states on mode change.
         this.on( 'mode', updateCommands );
         this.on( 'readOnly', updateCommands );
         this.on( 'selectionChange', updateCommandsContext );

         // Handle startup focus.
         this.on( 'instanceReady', function() {
            this.config.startupFocus && this.focus();
         });

         VED.fire( 'instanceCreated', null, this );

         // Add this new editor to the VED.instances collections.
         VED.add( this );

         // Return the editor instance immediately to enable early stage event registrations.
         VED.tools.setTimeout( function() {
            initConfig( this, instanceConfig );
         }, 0, this );
      }
   });

   VED.editor = Editor;

   var nameCounter = 0;

   function genEditorName() {
      do {
         var name = 'editor' + ( ++nameCounter );
      }
      while ( VED.instances[ name ] )

      return name;
   }

   function updateCommands() {
      var command,
         commands = this.commands,
         mode = this.mode;

      if ( !mode )
         return;

      for ( var name in commands ) {
         command = commands[ name ];
         command[ command.startDisabled ? 'disable' : this.readOnly && !command.readOnly ? 'disable' : command.modes[ mode ] ? 'enable' : 'disable' ]();
      }
   }

   function updateCommandsContext( ev ) {
      var command,
         commands = this.commands,
         editor = ev.editor,
         path = ev.data.path;

      for ( var name in commands ) {
         command = commands[ name ];

         if ( command.contextSensitive )
            command.refresh( editor, path );
      }
   }

   // ##### START: Config Privates

   // These function loads custom configuration files and cache the
   // VED.editorConfig functions defined on them, so there is no need to
   // download them more than once for several instances.
   var loadConfigLoaded = {};
   
   function loadConfig( editor ) {
		var customConfig = editor.config.customConfig;

		// Check if there is a custom config to load.
		if ( !customConfig )
			return false;

		customConfig = VED.getUrl( customConfig );

		var loadedConfig = loadConfigLoaded[ customConfig ] || ( loadConfigLoaded[ customConfig ] = {} );

		// If the custom config has already been downloaded, reuse it.
		if ( loadedConfig.fn ) {
			// Call the cached VED.editorConfig defined in the custom
			// config file for the editor instance depending on it.
			loadedConfig.fn.call( editor, editor.config );

			// If there is no other customConfig in the chain, fire the
			// "configLoaded" event.
			if ( VED.getUrl( editor.config.customConfig ) == customConfig || !loadConfig( editor ) )
				editor.fireOnce( 'customConfigLoaded' );
		} else {
			// Load the custom configuration file.
			VED.scriptLoader.load( customConfig, function() {
				// If the VED.editorConfig function has been properly
				// defined in the custom configuration file, cache it.
				if ( VED.editorConfig )
					loadedConfig.fn = VED.editorConfig;
				else
					loadedConfig.fn = function() {};

				// Call the load config again. This time the custom
				// config is already cached and so it will get loaded.
				loadConfig( editor );
			});
		}

		return true;
	}

   function initConfig( editor, instanceConfig ) {
      // Setup the lister for the "customConfigLoaded" event.
		editor.on( 'customConfigLoaded', function() {
			if ( instanceConfig ) {
				// Register the events that may have been set at the instance
				// configuration object.
				if ( instanceConfig.on ) {
					for ( var eventName in instanceConfig.on ) {
						editor.on( eventName, instanceConfig.on[ eventName ] );
					}
				}

				// Overwrite the settings from the in-page config.
				VED.tools.extend( editor.config, instanceConfig, true );

				delete editor.config.on;
			}

			onConfigLoaded( editor );
		});

		// The instance config may override the customConfig setting to avoid
		// loading the default ~/config.js file.
		if ( instanceConfig && instanceConfig.customConfig != undefined )
			editor.config.customConfig = instanceConfig.customConfig;

		// Load configs from the custom configuration files.
		if ( !loadConfig( editor ) )
			editor.fireOnce( 'customConfigLoaded' );

   }

   // ##### END: Config Privates

   function onConfigLoaded( editor ) {
      // Set config related properties.
      editor.readOnly = !!( editor.config.readOnly || ( editor.elementMode == VED.ELEMENT_MODE_INLINE ? editor.element.isReadOnly() : editor.elementMode == VED.ELEMENT_MODE_REPLACE ? editor.element.getAttribute( 'disabled' ) : false ) );

      editor.blockless = editor.elementMode == VED.ELEMENT_MODE_INLINE && !VED.dtd[ editor.element.getName() ][ 'p' ];

      editor.tabIndex = editor.config.tabIndex || editor.element && editor.element.getAttribute( 'tabindex' ) || 0;

      // Set VED.skinName. Note that it is not possible to have
      // different skins on the same page, so the last one to set it "wins".
      if ( editor.config.skin )
         VED.skinName = editor.config.skin;

      // Fire the "configLoaded" event.
      editor.fireOnce( 'configLoaded' );

      initComponents( editor );
   }

   // Various other core components that read editor configuration.
   function initComponents( editor ) {
      // Documented in dataprocessor.js.
      editor.dataProcessor = new VED.htmlDataProcessor( editor );
      loadSkin( editor );
   }

   function loadSkin( editor ) {
      VED.skin.loadPart( 'editor', function() { // !!!!!!!!!!!!!!
			loadLang( editor );
		});
   }

   function loadLang( editor ) {
      editor.langCode = editor.config.language || editor.config.defaultLanguage;

      var lang = VED.lang[ editor.langCode ];

      // As we'll be adding plugin specific entries that could come
      // from different language code files, we need a copy of lang,
      // not a direct reference to it.
      editor.lang = VED.tools.prototypedCopy( lang );

      // We're not able to support RTL in Firefox 2 at this time.
      if ( VED.env.gecko && VED.env.version < 10900 && editor.lang.dir == 'rtl' )
         editor.lang.dir = 'ltr';

      if ( !editor.config.contentsLangDirection ) {
         // Fallback to either the editable element direction or editor UI direction depending on creators.
         editor.config.contentsLangDirection = editor.elementMode == VED.ELEMENT_MODE_INLINE ? editor.element.getDirection( 1 ) : editor.lang.dir;
      }

      editor.fire( 'langLoaded' );

      loadPlugins( editor );
   }

   function loadPlugins( editor ) {
      var config = editor.config,
         plugins = config.plugins,
         extraPlugins = config.extraPlugins,
         removePlugins = config.removePlugins;

      if ( extraPlugins ) {
         // Remove them first to avoid duplications.
         var extraRegex = new RegExp( '(?:^|,)(?:' + extraPlugins.replace( /\s*,\s*/g, '|' ) + ')(?=,|$)', 'g' );
         plugins = plugins.replace( extraRegex, '' );

         plugins += ',' + extraPlugins;
      }

      if ( removePlugins ) {
         var removeRegex = new RegExp( '(?:^|,)(?:' + removePlugins.replace( /\s*,\s*/g, '|' ) + ')(?=,|$)', 'g' );
         plugins = plugins.replace( removeRegex, '' );
      }

      // Load the Adobe AIR plugin conditionally.
      VED.env.air && ( plugins += ',adobeair' );

      // Load all plugins defined in the "plugins" setting.
      VED.plugins.load( plugins.split( ',' ), function( plugins ) {
         // The list of plugins.
         var pluginsArray = [];

         // The language code to get loaded for each plugin. Null
         // entries will be appended for plugins with no language files.
         var languageCodes = [];

         // The list of URLs to language files.
         var languageFiles = [];

         editor.plugins = plugins;

         // Loop through all plugins, to build the list of language
         // files to get loaded.
         //
         // Check also whether any of loaded plugins doesn't require plugins
         // defined in config.removePlugins. Throw non-blocking error if this happens.
         for ( var pluginName in plugins ) {
            var plugin = plugins[ pluginName ],
               pluginLangs = plugin.lang,
               lang = null,
               requires = plugin.requires,
               match, name;

            // Transform it into a string, if it's not one.
            if ( VED.tools.isArray( requires ) )
               requires = requires.join( ',' );

            if ( requires && ( match = requires.match( removeRegex ) ) ) {
               while ( ( name = match.pop() ) ) {
                  VED.tools.setTimeout( function( name, pluginName ) {
                     throw new Error( 'Plugin "' + name.replace( ',', '' ) + '" cannot be removed from the plugins list, because it\'s required by "' + pluginName + '" plugin.');
                  }, 0, null, [ name, pluginName ] );
               }
            }

            // If the plugin has "lang".
            if ( pluginLangs && !editor.lang[ pluginName ] ) {
               // Trasnform the plugin langs into an array, if it's not one.
               if ( pluginLangs.split )
                  pluginLangs = pluginLangs.split( ',' );

               // Resolve the plugin language. If the current language
               // is not available, get English or the first one.
               if ( VED.tools.indexOf( pluginLangs, editor.langCode ) >= 0 )
                  lang = editor.langCode;
               else {
                  // The language code may have the locale information (zh-cn).
                  // Fall back to locale-less in that case (zh).
                  var langPart = editor.langCode.replace( /-.*/, '' );
                  if ( langPart != editor.langCode && VED.tools.indexOf( pluginLangs, langPart ) >= 0 )
                     lang = langPart;
                  // Try the only "generic" option we have: English.
                  else if ( VED.tools.indexOf( pluginLangs, 'en' ) >= 0 )
                     lang = 'en';
                  else
                     lang = pluginLangs[ 0 ];
               }

               if ( !plugin.langEntries && !plugin.langEntries[ lang ] ) {
						// Put the language file URL into the list of files to
						// get downloaded.
						languageFiles.push( VED.getUrl( plugin.path + 'lang/' + lang + '.js' ) );
					} else {
                  editor.lang[ pluginName ] = plugin.langEntries[ lang ];
                  lang = null;
               }
            }

            // Save the language code, so we know later which
            // language has been resolved to this plugin.
            languageCodes.push( lang );

            pluginsArray.push( plugin );
         }

         // Load all plugin specific language files in a row.
         VED.scriptLoader.load( languageFiles, function() {
            // Initialize all plugins that have the "beforeInit" and "init" methods defined.
            var methods = [ 'beforeInit', 'init', 'afterInit' ];
            for ( var m = 0; m < methods.length; m++ ) {
               for ( var i = 0; i < pluginsArray.length; i++ ) {
                  var plugin = pluginsArray[ i ];

                  // Uses the first loop to update the language entries also.
                  if ( m === 0 && languageCodes[ i ] && plugin.lang && plugin.langEntries )
                     editor.lang[ plugin.name ] = plugin.langEntries[ languageCodes[ i ] ];

                  // Call the plugin method (beforeInit and init).
                  if ( plugin[ methods[ m ] ] )
                     plugin[ methods[ m ] ]( editor );
               }
            }

            editor.fireOnce( 'pluginsLoaded' );

            // Setup the configured keystrokes.
            config.keystrokes && editor.setKeystroke( editor.config.keystrokes );

            // Setup the configured blocked keystrokes.
            for ( i = 0; i < editor.config.blockedKeystrokes.length; i++ )
               editor.keystrokeHandler.blockedKeystrokes[ editor.config.blockedKeystrokes[ i ] ] = 1;

            editor.fireOnce( 'loaded' );
            VED.fire( 'instanceLoaded', null, editor );
         });
      });
   }

   // Send to data output back to editor's associated element.
   function updateEditorElement() {
      var element = this.element;

      // Some editor creation mode will not have the
      // associated element.
      if ( element && this.elementMode != VED.ELEMENT_MODE_APPENDTO ) {
         var data = this.getData();

         if ( this.config.htmlEncodeOutput )
            data = VED.tools.htmlEncode( data );

         if ( element.is( 'textarea' ) )
            element.setValue( data );
         else
            element.setHtml( data );

         return true;
      }
      return false;
   }

   VED.editor.implement({

      addCommand: function( commandName, commandDefinition ) {
         return this.commands[ commandName ] = new VED.command( this, commandDefinition );
      },

      destroy: function( noUpdate ) {
         this.fire( 'beforeDestroy' );

         !noUpdate && updateEditorElement.call( this );

         this.editable( null );

         this.fire( 'destroy' );

         // Plug off all listeners to prevent any further events firing.
         this.removeAllListeners();

         VED.remove( this );
         VED.fire( 'instanceDestroyed', null, this );
      },

      elementPath: function( startNode ) {
         startNode = startNode || this.getSelection().getStartElement();
         return startNode ? new VED.dom.elementPath( startNode, this.editable() ) : null;
      },

      createRange: function() {
         var editable = this.editable();
         return editable ? new VED.dom.range( editable ) : null;
      },

      execCommand: function( commandName, data ) {
         var command = this.getCommand( commandName );

         var eventData = {
            name: commandName,
            commandData: data,
            command: command
         };

         if ( command && command.state != VED.TRISTATE_DISABLED ) {
            if ( this.fire( 'beforeCommandExec', eventData ) !== true ) {
               eventData.returnValue = command.exec( eventData.commandData );

               // Fire the 'afterCommandExec' immediately if command is synchronous.
               if ( !command.async && this.fire( 'afterCommandExec', eventData ) !== true )
                  return eventData.returnValue;
            }
         }

         // throw 'Unknown command name "' + commandName + '"';
         return false;
      },

      getCommand: function( commandName ) {
         return this.commands[ commandName ];
      },

      getData: function( noEvents ) {
         !noEvents && this.fire( 'beforeGetData' );

         var eventData = this._.data;

         if ( typeof eventData != 'string' ) {
            var element = this.element;
            if ( element && this.elementMode == VED.ELEMENT_MODE_REPLACE )
               eventData = element.is( 'textarea' ) ? element.getValue() : element.getHtml();
            else
               eventData = '';
         }

         eventData = { dataValue: eventData };

         // Fire "getData" so data manipulation may happen.
         !noEvents && this.fire( 'getData', eventData );

         return eventData.dataValue;
      },

      getSnapshot: function() {
         var data = this.fire( 'getSnapshot' );

         if ( typeof data != 'string' ) {
            var element = this.element;
            if ( element && this.elementMode == VED.ELEMENT_MODE_REPLACE )
               data = element.is( 'textarea' ) ? element.getValue() : element.getHtml();
         }

         return data;
      },

      loadSnapshot: function( snapshot ) {
         this.fire( 'loadSnapshot', snapshot );
      },

      setData: function( data, callback, internal ) {
         if ( callback ) {
            this.on( 'dataReady', function( evt ) {
               evt.removeListener();
               callback.call( evt.editor );
            });
         }

         // Fire "setData" so data manipulation may happen.
         var eventData = { dataValue: data };
         !internal && this.fire( 'setData', eventData );

         this._.data = eventData.dataValue;

         !internal && this.fire( 'afterSetData', eventData );
      },

      setReadOnly: function( isReadOnly ) {
         isReadOnly = ( isReadOnly == undefined ) || isReadOnly;

         if ( this.readOnly != isReadOnly ) {
            this.readOnly = isReadOnly;

            this.editable().setReadOnly( isReadOnly );

            // Fire the readOnly event so the editor features can update
            // their state accordingly.
            this.fire( 'readOnly' );
         }
      },

      insertHtml: function( html, mode ) {
         this.fire( 'insertHtml', { dataValue: html, mode: mode } );
      },

      insertText: function( text ) {
         this.fire( 'insertText', text );
      },

      insertElement: function( element ) {
         this.fire( 'insertElement', element );
      },

      focus: function() {
         this.fire( 'beforeFocus' );
      },

      checkDirty: function() {
         return this._.previousValue !== this.getSnapshot();
      },

      resetDirty: function() {
         this._.previousValue = this.getSnapshot();
      },

      updateElement: function() {
         return updateEditorElement.call( this );
      },

      setKeystroke: function() {
         var keystrokes = this.keystrokeHandler.keystrokes,
            newKeystrokes = VED.tools.isArray( arguments[ 0 ] ) ? arguments[ 0 ] : [ [].slice.call( arguments, 0 ) ],
            keystroke, behavior;

         for ( var i = newKeystrokes.length; i--; ) {
            keystroke = newKeystrokes[ i ];
            behavior = 0;

            // It may be a pair of: [ key, command ]
            if ( VED.tools.isArray( keystroke ) ) {
               behavior = keystroke[ 1 ];
               keystroke = keystroke[ 0 ];
            }

            if ( behavior )
               keystrokes[ keystroke ] = behavior;
            else
               delete keystrokes[ keystroke ];
         }
      }
   });
})();

VED.ELEMENT_MODE_NONE = 0;
VED.ELEMENT_MODE_REPLACE = 1;
VED.ELEMENT_MODE_APPENDTO = 2;
VED.ELEMENT_MODE_INLINE = 3;





